Remotehost Docs

Platform Guide

Give your own users a sandbox each, without giving them a RemoteHost account

This guide is for building a product on RemoteHost: your users click something in your app, and a sandbox appears for them. They never sign up for RemoteHost, never see it, and never know it is there.

Everything below is the REST API. Your backend holds one API key; your users hold nothing.

Get a key

remote keys create --name production-backend

Shown once, stored only as a hash. A key acts as the user who created it, is scoped to one org, and can reach only the routes a platform needs — it cannot create orgs, move billing, invite people, or mint another key. Give it a lifetime with --expires-in-days, list keys with remote keys list, and kill one with remote keys revoke <id>.

curl https://api.remotehost.ai/v1/me/orgs -H "Authorization: Bearer $REMOTEHOST_API_KEY"

One sandbox per user

Tag every sandbox with the id that user has in your system. That id is the spine of everything else here — filtering, attribution, and the bill you send them.

curl -X POST https://api.remotehost.ai/v1/orgs/$ORG/sandboxes \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "projectId": "'$PROJECT'",
    "agent": "claude",
    "endUserId": "usr_8f21",
    "metadata": { "plan": "pro", "signup_cohort": "2026-09" }
  }'

endUserId is opaque to us. We never parse it, never join on it, and never assume it is unique — use your primary key, an email, a hash, whatever you already have. metadata is flat labels (strings, numbers, booleans; 24 keys) for anything else you want to carry.

Then find their sandboxes without paging your whole org:

curl "https://api.remotehost.ai/v1/orgs/$ORG/sandboxes?endUserId=usr_8f21" \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY"

One project per customer works too, and a key can create projects (POST /v1/orgs/{orgId}/projects), so you never have to click through a console to onboard one. Use projects for hard boundaries — separate repo credentials, separate machine-hour caps — and endUserId for everything else. A project is the heavier object; you do not want ten thousand.

Show them their app

A sandbox is only useful to your user if they can see what is running inside it. Mint a public preview link for a port:

curl -X POST https://api.remotehost.ai/v1/sandboxes/$SANDBOX/previews \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "port": 3000, "audience": "public", "expiresInSeconds": 3600, "endUserId": "usr_8f21" }'
{
  "preview": {
    "id": "9d1c...",
    "audience": "public",
    "port": 3000,
    "url": "https://3000--sb-8f21.previews.remotehost.ai/?__remotehost_preview=...",
    "expiresAt": "2026-09-08T12:00:00.000Z"
  }
}

Anyone holding that URL can open it. No RemoteHost account, no sign-in, no cookie.

The default is "audience": "team", which is the older behaviour: the URL works only for members of your org. Nothing becomes public unless you ask for it, and asking requires the sandbox.preview.share permission — an operator or a viewer can watch a sandbox but cannot publish one.

Every public link is revocable on its own.

curl -X DELETE https://api.remotehost.ai/v1/sandboxes/$SANDBOX/previews/$LINK \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY"

That matters more than it sounds. A signed URL with no row behind it cannot be taken back without invalidating every other URL you have ever issued. Here the token names a link, the link is a database row, and revoking is an update — one customer's leaked URL is one customer's problem. Links stop working within seconds across every API instance, expire on their own schedule (24 hours by default, 30 days maximum), and GET /v1/sandboxes/{id}/previews lists what is outstanding, including when each was last used.

Run their code

curl -X POST https://api.remotehost.ai/v1/sandboxes/$SANDBOX/exec \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "command": "pnpm install && pnpm dev &", "timeoutSeconds": 120 }'

exec waits for the command to exit and returns exitCode, stdout and stderr, so background anything long-lived. You are not obliged to use the agent that ships in the sandbox: run your own daemon, expose its port, and drive it from your own infrastructure.

Pay for what runs

The three lifecycle shapes, cheapest last:

PatternHowCost shape
Always onpersistencePolicy: "always_on"Bills continuously
Sleep and resumepersistencePolicy: "sleep_resume"Bills while awake; disk kept
Ephemeralmode: "ephemeral" with ttlSecondsBills until the TTL, then gone

POST /v1/sandboxes/{id}/sleep stops a sandbox and keeps its disk; wake brings it back from the snapshot. destroy is the end — the disk and its snapshots go with it. Resume on your user's first interaction and sleep them on idle, and a user who works two hours a week costs two hours.

Ephemeral sandboxes are only allowed on projects marked useCase: "external", which is the setting that says "these machines are for the people using my product, not for my engineers".

Bill your users

Usage is attributed to the endUserId that was set when the machine ran — denormalised onto each usage row, so it survives the sandbox being deleted and stays correct if you later reassign one.

curl https://api.remotehost.ai/v1/orgs/$ORG/usage/end-users \
  -H "Authorization: Bearer $REMOTEHOST_API_KEY"
{
  "periodStartsAt": "2026-09-01T00:00:00.000Z",
  "endUsers": [
    { "endUserId": "usr_8f21", "machineHours": 12.4, "estimatedCogsUsd": 1.61 },
    { "endUserId": "usr_3b09", "machineHours": 0.8, "estimatedCogsUsd": 0.1 }
  ]
}

That is the whole billing story: our invoice to you is one number, and this is how you split it. Machine hours still running are included, so the figure moves during the period rather than jumping at the end. GET /v1/orgs/{orgId}/usage?endUserId=usr_8f21 gives one user against the plan's limits.

Guardrails

  • Concurrency. Your plan caps how many sandboxes run at once; POST /sandboxes returns 429 with the limit in the message when you reach it. Size it before you launch: roughly 10,000 signups tends to mean 100 concurrent sandboxes.
  • Machine hours. Cap a project with sandboxMachineHourLimit, or the whole org with usage guardrails, so a runaway loop is a stopped sandbox rather than an invoice.
  • Rate limit. 600 requests a minute per API key, with Retry-After on the 429.
  • Blast radius. A key is scoped to one org and cannot mint another key, so a leaked key is revoked in one call and cannot have created a second way in.

What we do not have yet

Said plainly, because finding out later is worse:

  • No webhooks. Sandbox lifecycle is polled through GET /v1/sandboxes/{id}, not pushed.
  • No custom templates. Sandboxes snapshot and resume individually, but you cannot yet build an image once and fork it per user; each sandbox provisions from the standard base.
  • Agent is required. create_sandbox takes claude or codex even if you intend to run your own daemon and ignore both.

If any of those decide it for you, tell us — they are the next things on this list.

On this page