Skip to content
Ibby docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Concepts

Tenants, lanes and keys; sandboxes and their limits; images; jobs; safe retries; and errors.

Tenants, lanes and keys

A tenant is one customer of the platform, usually one app. A lane is one of that tenant’s environments, for example staging or production. Lanes are kept apart: each has its own sandboxes, limits and keys.

An API key belongs to exactly one lane, and the lane is always taken from the key. A key looks like ibk_<public id>_<secret>. It is shown once, when it is made, and the platform keeps only a digest of it. Send it as a bearer token:

Authorization: Bearer ibk_...

A call with a missing, unknown or revoked key gets 401 unauthorized. A key used on another lane’s sandbox, job or image gets 404 not_found, so ids never leak between lanes. Usage shows only the key’s own lane.

Sandboxes

A sandbox is an isolated place to run code. Each one:

  • has no network and holds no secrets;
  • has a working directory, made when it is created;
  • lives for a set time, two minutes unless you ask for up to five, then stops on its own;
  • gets the lane’s CPU and memory, unless you ask for other values within the lane’s caps;
  • runs on the lane’s default image, unless you name an image you built;
  • may have part of the lane’s store folder attached, read-only;
  • may have a name of your own, unique among the lane’s running sandboxes, so you can remove it by name after a crash.

A sandbox is pending while it is being made, then running, then stopped. A call on a stopped sandbox gets 409 sandbox_stopped, except reading its status, which shows stopped, and removing it, which succeeds. New statuses may be added, so treat one you do not know as “not running”.

A read-only store folder

A lane may have a store folder attached: files your app keeps in the platform’s storage, under the lane’s own root. A create can attach part of it, read-only, with store:

{
  "workdir": "/work",
  "store": { "mount_path": "/work/in", "key_prefix": "runs/1/" }
}

mount_path is an absolute path inside workdir. key_prefix is relative to the lane’s root: no leading /, it ends in /, and it has no . or .. part. The sandbox sees only the files under that prefix, and cannot change them. A prefix the platform does not allow gets 400 store_prefix_refused, and a lane with no store folder gets 409 store_unavailable. The sandbox record shows the attached folder as store, or null.

Running commands

A command is an argument list, not a shell line: ["python", "-c", "print(1)"]. To use a shell, run one: ["sh", "-c", "ls | wc -l"]. You can pass a working directory, up to 8 environment variables and a timeout of up to 120 seconds.

A command ends one of two ways. It exited, with its exit code, or it timed out and was stopped. A timeout is a normal result, not an error. Standard output comes back cut at the lane’s limit (64 KiB unless your lane says otherwise), with a flag that says whether it was cut. In this version a command that timed out returns no output: stdout is null.

Files

Files move through the API as bytes, up to 32 MiB per call. One call can write several files. Reading a file that does not exist gets 404 file_not_found; a file larger than you allow gets 413 file_too_large.

Images

An image is what a sandbox runs on. Each lane has a default image. To run on your own, write a recipe and build it:

  • a base image, pinned by its sha256 digest;
  • the Python packages to install, each at one exact version, with the hashes of its files;
  • optionally, modules to import once the packages are in, as a check that the build works.

A recipe has no build commands, so the same recipe always builds the same image. Nothing is installed that is not listed with its hash.

{
  "base": {
    "reference": "python:3.13-slim",
    "digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  },
  "packages": [
    {
      "name": "numpy",
      "version": "2.3.1",
      "hashes": [
        "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
      ]
    }
  ],
  "import_check": ["numpy"]
}

A build is a job of kind image_build. When it is done, its result names the image: { "image_id": "img_..." }. When it is failed, its error.code says why:

Code Meaning
build_failed The build itself failed, for example a package would not install.
import_check_failed The image built, but a module in import_check would not import.
build_lost The platform lost track of the build. Build the recipe again.
unavailable A service behind the API failed. Build the recipe again after a short wait.

New codes may be added, so treat a code you do not know as a failure. An image is building, then ready or failed. Create a sandbox with image set to a ready image’s id. A create from an image that is not ready gets 409 image_not_built. Getting an image can also answer 409 image_not_built, after a lane changes provider: the image has no build on the lane’s current provider, so rebuild it from its recipe.

Jobs

Work that takes longer than one call returns a job at once. You check it until it is done, failed or cancelled, and you can ask to cancel it. A job moves forward only: accepted, running, then one of the three final states.

Safe retries

Every call that creates or writes takes an Idempotency-Key. A retry with the same key and the same body is the same call:

  • a create returns the same sandbox;
  • a file write returns its first result;
  • a call that is still running gets 409 in_progress; retry it later with the same key;
  • a command that has finished returns its outcome and exit code, but not its output, which the platform does not keep.

The same key with a different body gets 409 idempotency_mismatch. Keys are kept for 24 hours.

The SDKs and the command line tool send a fresh key on every such call, and retry up to twice on their own after a dropped connection or a 503, reusing the key. They never retry a command on their own, because a replay returns no output, and they never retry a 409 in_progress.

Errors

Every error has the same shape, with a stable code, a message for people and the request’s id:

{
  "error": {
    "code": "sandbox_stopped",
    "message": "The sandbox has stopped: its lifetime or size limit was reached, or it was removed.",
    "request_id": "req_..."
  }
}

Every response also carries the request id in its X-Request-Id header. Quote it when you report a problem. New codes may be added over time, so handle an unknown code by its HTTP status. The API reference lists the codes each operation can return.

Was this page helpful?