Base URL
Authentication
Send your API key as a bearer token on every request.user_id tells you which member of the organization the key belongs to. Compare
it against a Sailbox’s created_by_user_id to tell your own Sailboxes apart from
a teammate’s. Service keys have no user, so user_id is null.
A Sailbox created with "visibility": "private" can only be operated by the user
whose key created it. An organization admin can still pause, resume, sleep,
schedule a wake, upgrade, or terminate one by sending an
X-Sail-Owner-Override-Reason header saying why. The reason goes into your
organization’s audit log, and the operation is refused without it. Publishing and
unpublishing ports, forking, checkpointing, and creating from a checkpoint stay
with the creator.
What this API covers
Over HTTP you can create Sailboxes, run their whole lifecycle, publish and unpublish ports, serve them on hostnames you own, manage volumes, and read status, metrics, and spend. Three things need an SDK or the CLI:- Running commands and moving files. This API has no endpoint for running a
command or transferring a file. Use an SDK or the CLI, or turn on SSH and use
sshandscp. - Turning on SSH. SSH has to be enabled inside the Sailbox once, and that step needs an SDK or the CLI. After that the rest is HTTP: issue certificates and publish port 22 for as long as the Sailbox lives. See Networking.
- Building an image. Creating a Sailbox needs an image that is ready to
boot: a base image on its own, or one built earlier from the same
imageblock. Building an image with your own packages, environment variables, or Python version takes an SDK or the CLI, and asking for one that has never been built returns 409. Once the image is built, you can create Sailboxes from it over HTTP as often as you like.
Create your first Sailbox
Export your key so the commands below run as written.id you get back in the requests below. image says what the Sailbox
boots into. A plain Debian base needs nothing built first, which is what this
example uses.
Idempotency-Key is what makes a create safe to retry. Run the same
command again while $KEY still holds the same value and the retry returns the
first attempt’s answer instead of creating a second Sailbox. Any unique string
works.
Take a fresh $KEY for every new Sailbox you mean to create. A create that
comes back 500, 503, or 504, or that never comes back at all, is the one case
that needs a closer look, because the Sailbox can exist even though the call
failed. See Retrying safely.
This request blocks until startup finishes, so set a generous client timeout. A
create can take a few minutes, because it waits for a machine to run on.
Read status before you use the Sailbox. A create that is accepted and then
cannot bring the machine up still returns 200, with status set to failed and
error_message giving the reason. A client that checks only the HTTP status will
think it has a Sailbox it does not have.
When you are finished, terminate the Sailbox so it stops costing money. Use the
sailbox_id you got back from the create:
Errors
Failures come back with an HTTP status and a JSON body in one shape:
A URL that is not an endpoint returns 404, and an endpoint called with the wrong
method returns 405. Both use
invalid_request_error.
Retry 500, 503, and 504 with exponential backoff. Send an Idempotency-Key on
every POST that creates or changes a Sailbox, a listener, or a volume, on the
first attempt and on every retry, so a retry after a timeout replays the first
answer instead of running the operation again. A key does not cover every case: a
server error, or a failure to record the answer, can leave the retry to run the
request again. Creating a Sailbox is where that matters. See
Retrying safely.
The Retry-After header tells the two 429s apart. With one, the request never
started: wait that many seconds and retry, and the same Idempotency-Key is
fine. Without one, the request ran and hit the limit message names.
Some of those limits clear on their own, such as a shortage of public ports.
Others need you to free something up first. Once the cause is gone, send the
request again under a fresh Idempotency-Key, because the old key now replays
this answer.
Do not resend a 400, 401, 402, 403, 404, or 413 unchanged, because the same
request fails the same way. Fix it first. Registering a custom domain is the one
exception: it answers 400 until your DNS record resolves, and that request is
worth sending again once it does, as long as your DNS provider is not proxying
the record. A 409 is usually the same, but some
conflicts clear on their own: a volume you could not delete becomes deletable
once the Sailbox mounting it terminates, and an operation refused because another
one is already running on that Sailbox succeeds once that one finishes. message
tells you which you have.
A proxy between you and Sail can also return a 502 or a 504 with no JSON body at
all. Treat those the same way: retry with backoff.
Match on the HTTP status and on type. Treat message as text for humans: it is
written to be readable and can change.
Retrying safely
EveryPOST that creates or changes a Sailbox, a listener, or a volume accepts
an Idempotency-Key header, and it is the safest way to handle an ambiguous
failure such as a timeout or a 502. A retry that sends the same key and the same
body gets the first request’s response instead of starting a second operation.
- A replayed response carries
Idempotent-Replayed: true. - If the original request is still running when the retry arrives, the retry waits for it and returns its result. If the original is still going after 30 seconds, the retry gets a 504 and you can retry again with the same key.
Idempotency-Key. A key sent there is
ignored, so retry it on its own terms.
Listing and pagination
GET /sailboxes returns a page at a time.
limit and offset, and stop when has_more is false. limit goes
up to 100.
Filters combine: app, status, and search narrow the list, and
manageable_by_caller=true hides private Sailboxes someone else created,
whether or not you could operate them with an override.
Checking a resume result
Resume returns 200 whether or not the Sailbox came back, and reports what happened inresume_state:
running: the Sailbox is ready now.already_running: it never stopped, so there was nothing to do.terminal_unavailable: it can never resume, anderror_messagesays why. Create a new Sailbox instead of retrying.
Handling fields you do not recognize
Bothstatus and resume_state are open sets. New values get added as the
service grows, and a client that treats an unfamiliar value as an error breaks
the first time it sees one. Match the values you care about, and pass anything
else through untouched.
Responses also grow new fields over time. Ignore fields you do not recognize
rather than rejecting the response.
Limits
Most endpoints that take a request body cap it at 64 KiB.POST /sailboxes
accepts up to 256 MiB, which is what lets a large image definition through.
POST /apps/find shares the larger cap.
For its first ten minutes, a new organization is also capped on how many
requests it can have in flight at once. The cap counts every request from the
organization, not just yours. Over it you get a 429 with a Retry-After
header, and it clears as those requests finish.