Handle errors

Branch on an error’s code, show what was wrong with the input, and decide when to try again.

Every function in the SDK fails the same way: it throws a SpaceError, or its promise rejects with one. A SpaceError carries a stable code to branch on, a message for a person, and the details you need to try again or ask us for help. See SpaceError for every field and code.

Branch on the code

Check the error with isSpaceError, then branch on code. The code never changes for a given failure. The message may be reworded, so never branch on it.

read-invoice.ts
import { isSpaceError, space } from "digitospace"

export async function readInvoice(number: string): Promise<string | null> {
  try {
    return await space.fs.read(`invoices/2026/${number}.txt`, { as: "text" })
  } catch (error) {
    if (isSpaceError(error) && error.code === "not_found") return null
    throw error
  }
}

Codes come from two places:

  • The SDK itself, for failures before a call is sent or after an answer arrives, such as validation_failed, missing_api_key, timeout, or unexpected_response.
  • The service, such as bucket_name_taken from storage or unit_limit_reached from AI. They pass through unchanged, so the codes in each product’s error reference are the codes you see.

status is the HTTP status the service answered with, or 0 when the failure happened in the SDK.

Show what was wrong with the input

A value that is not in the right form fails with validation_failed before anything is sent. The same code comes back when the service refuses a field. issues lists every field that is wrong, each with a field, a code, and a message.

create-bucket.ts
import { isSpaceError, space } from "digitospace"

try {
  await space.fs.buckets.create("Invoices 2026")
} catch (error) {
  if (isSpaceError(error) && error.code === "validation_failed") {
    for (const issue of error.issues) {
      console.log(`${issue.field}: ${issue.message}`)
    }
  } else {
    throw error
  }
}

A field inside an object is named with dots, such as target.url for a job’s target, so you can show each message next to the input it belongs to.

Retries the SDK already makes

The SDK tries a call again on its own, up to 2 more times by default, with a growing wait between tries:

  • after 429 or 503, for any call, because the service refused it before doing anything. A Retry-After header sets the wait, up to 30 seconds.
  • after a network failure, a timeout, or any other status of 502 or higher, only for a call that is safe to repeat, such as a read, a PUT, or a delete.

A write that may already have happened, such as creating a job or sending a chat call, is never sent twice. Change how many tries it makes with the retries option. See Retries.

Try again yourself

When the SDK has used its tries, or would not repeat a call, the error says whether trying later could work: retryable is true for a busy service, a network failure, a timeout, or an interrupted stream. Try again only then, and wait longer each time.

with-retry.ts
import { isSpaceError, space } from "digitospace"

async function withRetry<T>(work: () => Promise<T>, tries = 3): Promise<T> {
  for (let attempt = 1; ; attempt++) {
    try {
      return await work()
    } catch (error) {
      if (!isSpaceError(error) || !error.retryable || attempt >= tries) throw error
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt))
    }
  }
}

const reply = await withRetry(() =>
  space.ai.chat("Name three cities in Tanzania.", { model: "openai/gpt-oss-20b" })
)
console.log(reply.text)

Before you repeat a write that failed with a network failure or a timeout, check whether it happened. The call may have reached the service before the connection broke.

A code with retryable set to false, such as forbidden or validation_failed, fails the same way every time. Fix the cause instead.

Quote the request ID

Every call the service answered carries a requestId. Log it with the error, and quote it when you ask us for help, so we can find the call.

log-error.ts
import { isSpaceError, space } from "digitospace"

try {
  await space.fs.delete("invoices/2026/0001.txt")
} catch (error) {
  if (isSpaceError(error)) {
    console.error(error.code, error.status, error.requestId, error.message)
  }
  throw error
}

requestId is undefined when the call never reached the service, such as for validation_failed from the SDK, timeout, or network_error.

Last updated