One API. Four ways in.
Everything Rytiv does goes through one HTTP API. The CLI, the console and the MCP server use the same routes you can call yourself, with the same keys. Push to GitHub, type ry, write fetch, or let your agent do it.
curl https://api.rytiv.com/v1/apps -H "Authorization: Bearer $RYTIV_TOKEN" const res = await fetch("https://api.rytiv.com/v1/apps", { headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}` }, }); const apps = await res.json();
import os, requests res = requests.get( "https://api.rytiv.com/v1/apps", headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"}, ) apps = res.json()
ry apps list The same call four ways, as every docs page shows it. The base URL is https://api.rytiv.com/v1.
Same keys, same apps, whichever door.
All four see the same apps, databases and machines. The console at console.rytiv.com is where you sign in, create keys, wake a computer and top up credit.
Deploy on push
Link a repository to an app. Every push to the branch builds and deploys; other branches open a protected preview that closes on its own.
ry git connect web --repo acme/web --branch main ry, in a terminal or in CI
Signs in with a code, reads rytiv.json, and does what the console does. Every command takes --json for scripts and agents.
ry login ry deploy web --source .
Your own code
The API reference is generated from the control plane's routes: every field, bound and error code is what the code enforces.
curl https://api.rytiv.com/v1/apps -H "Authorization: Bearer $RYTIV_TOKEN" Your coding agent
One endpoint, 24 tools, annotated so your client knows what to confirm. Claude Code, Codex or any client over Streamable HTTP.
claude mcp add --transport http rytiv https://api.rytiv.com/v1/mcp --header "Authorization: Bearer <your key>" From a folder to a URL, then to your agent.
Six commands from the docs, in order. To run machines an account needs credit: the starting credit if it qualifies, otherwise a first top-up.
-
Install the CLI
A Node.js package, Node.js 22.6 or newer. The installer checks the published checksum before it installs anything.
terminal curl -fsSL https://get.rytiv.com/install.sh | sh -
Sign in
ry loginshows a code you confirm in the console, and stores a key for the CLI. In CI or a container, usery login --token <key>orRYTIV_TOKENinstead.terminal ry login -
Deploy a folder
The app is created if it does not exist. The CLI prints what it detected, uploads, builds on Rytiv and rolls the deploy. The last line is your URL on rytiv.app.
in your project ry deploy web --source . -
Watch it run
Stream the console output of the app's machines.
ry init websaves the settings inrytiv.json, so the next deploy needs no flags.terminal ry logs web -f ry init web
-
Deploy on push
Installs the GitHub app through the browser and links the repository. Every push to the branch deploys; other branches get a protected preview.
terminal ry git connect web --repo acme/web --branch main -
Hand your agent a key
An agent key that can do what your organization can, except touch credentials, limited to the apps you name. Then add it to Claude Code.
terminal ry keys create claude-code --agent sandbox --app web
Or run ry on its own in a project directory: it signs you in if needed, shows what it detected, asks for an app name and deploys. Deploy a Next.js app walks through secrets, releases and rollback.
A bearer key, JSON, one error format.
Requests and answers are JSON. Every route acts as the organization of the key; a name of another organization answers exactly like one that does not exist.
Base URL and key
https://api.rytiv.com/v1 Authorization: Bearer ryt_... Keys start with ryt_. Rytiv stores only a hash, so a key is shown exactly once. The product API has no general rate limit; the MCP endpoint, shell sessions, viewer links and the login entrances do, and answer 429 with Retry-After.
A deploy, a branch or a fork answer 202 and continue in the background. Poll the object they name until it is done.
Errors: branch on code, show error
{ "error": "name already taken", "code": "name-taken" } | 400 | bad-request: malformed, or a field out of range. issues lists every field. |
| 401 | No API key, or one that does not work. |
| 402 | Credit at zero. Reads, billing, stops, deletes, detaching and creating keys still pass. |
| 403 | The key may not: a scope, an agent class, an organization limit. |
| 404 | Not found, or not yours. |
| 409 | Conflicts with the current state: a taken name, a deploy already running. |
| 429 | A limit or a rate limit. Retry-After says when. |
New codes can appear; a code your program does not know should fall back on the HTTP status. The error catalog lists every one.
A few lines of fetch is all it takes.
The API is plain HTTP with one error format, so any language reaches it. This small helper does the job: the key read from RYTIV_TOKEN, errors you can branch on.
const API = process.env.RYTIV_ENDPOINT ?? "https://api.rytiv.com"; async function rytiv(path: string, init: RequestInit = {}) { const res = await fetch(`${API}/v1${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json", ...init.headers, }, }); const body = res.status === 204 ? null : await res.json(); if (!res.ok) throw new Error(`${body?.code ?? res.status}: ${body?.error ?? res.statusText}`); return body; } const sandbox = await rytiv("/sandboxes", { method: "POST", body: JSON.stringify({ template: "app:web", ttlSeconds: 600 }), }); const result = await rytiv(`/sandboxes/${sandbox.id}/exec`, { method: "POST", body: JSON.stringify({ cmd: ["node", "--version"] }), }); console.log(result.stdout); await rytiv(`/sandboxes/${sandbox.id}`, { method: "DELETE" });
import os, requests API = os.environ.get("RYTIV_ENDPOINT", "https://api.rytiv.com") HEADERS = {"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"} def rytiv(method, path, **kwargs): res = requests.request(method, f"{API}/v1{path}", headers=HEADERS, **kwargs) if res.status_code == 204: return None body = res.json() if not res.ok: raise RuntimeError(f"{body.get('code', res.status_code)}: {body.get('error')}") return body sandbox = rytiv("POST", "/sandboxes", json={"template": "app:web", "ttlSeconds": 600}) result = rytiv("POST", f"/sandboxes/{sandbox['id']}/exec", json={"cmd": ["node", "--version"]}) print(result["stdout"]) rytiv("DELETE", f"/sandboxes/{sandbox['id']}")
The helper creates a sandbox from the image of the app web, runs node --version in it, prints the output and ends it. RYTIV_ENDPOINT overrides the address, like it does for the CLI. Your coding agent already has the platform as MCP tools.
One kind of key. Narrow it as far as you like.
The same key works for the REST API, the MCP server and the CLI. Without scopes it is the whole organization; scopes, an expiry, an agent class and an app list make it do less, never more. The platform enforces all of them.
Create a key for an agent
Creating a key needs a full key; your first one comes from the console or from ry login. The value is in the answer once.
ry keys create claude-code --agent sandbox --app web --app web-try curl -X POST "https://api.rytiv.com/v1/tokens" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "claude-code", "agent": {"class": "sandbox", "apps": ["web", "web-try"]}}'
const res = await fetch("https://api.rytiv.com/v1/tokens", { method: "POST", headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ name: "claude-code", agent: { class: "sandbox", apps: ["web", "web-try"] } }), }); const { token } = await res.json(); // shown in this answer only
import os, requests res = requests.post( "https://api.rytiv.com/v1/tokens", headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"}, json={"name": "claude-code", "agent": {"class": "sandbox", "apps": ["web", "web-try"]}}, ) token = res.json()["token"] # shown in this answer only
Scopes
| read | Every read of the product API that is a status. Not credentials, not billing, not the files of a machine. |
| deploy | The deploy routes of every app: build, deploy, releases, rollback, logs, machines, secrets. Never creating or deleting an app, never a key. |
| deploy:<app> | The same for exactly that app. |
| mail:send | Sending mail from any proven domain of the organization. |
| mail:send:<domain> | Sending from exactly that domain. |
ry keys create ci-web --deploy web --expires-in 30d ry keys create status-page --scope read
Expiry
A key may stop working at a moment or after a duration, from 60 seconds to 365 days ahead. A key with an expiry cannot create a key that outlives it. Revoking rejects requests already using it, right away.
Agent classes
sandbox and twin may do what your organization's keys do, except create, reveal or revoke credentials. production may only read. Every agent action is audited before it runs. More on agents.
Device login
ry login asks for a code, you confirm it in the console, and the CLI receives a key of your team that you can revoke like any other. Where nobody can open a browser: ry login --token <key> or RYTIV_TOKEN.
OAuth for hosted clients
A hosted MCP connector needs nothing but the address. OAuth 2.1 with dynamic registration and PKCE, no client secrets; you approve once in the console. Access tokens last an hour, refresh tokens 30 days.
Managed organizations
Once we enable you as a partner, your organization can manage others, and a key of yours can act inside one with the header X-Rytiv-Org. Agent tokens cannot use it.
Environment
RYTIV_TOKEN for the key, never written to disk. RYTIV_ENDPOINT for the address. RYTIV_PROFILE to pick one of several saved logins.
Be called when mail moves.
Register a URL for events of your organization; every delivery is recorded and can be read back over GET /v1/webhooks/:id/deliveries. The events are mail events: email.sent, email.delivery_delayed, email.bounced, email.complained, email.failed and email.received. Up to 20 webhooks per organization.
The answer to the create carries a signing secret, shown once. Registering and removing webhooks needs a full key; agent tokens are refused. Mail is in private beta and is for transactional mail only, no advertising or newsletters.
curl -X POST "https://api.rytiv.com/v1/webhooks" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url":"<url>","events":["email.sent"]}'
ry mail webhooks add https://www.example.com/hooks/rytiv ry mail webhooks list
Docs your agent can read without scraping.
Every page of the docs is also plain Markdown, generated from the same source. /llms.txt lists every page with a one-line description; /llms-full.txt is the whole documentation in one file. Every page has a Copy page button for pasting into a conversation.
The MCP tools page is generated from the table the server answers tools/list with, and the CLI reference from the CLI's own command definitions. If a parameter is listed, it is accepted. Inside a connected agent, search_docs answers from a knowledge base in the platform, without a request to the docs site.
curl https://docs.rytiv.com/llms.txt curl https://docs.rytiv.com/llms-full.txt curl https://docs.rytiv.com/quickstart.md
Every route, by family.
Your first deploy is one command away.
Deploy from a folder, or connect your coding agent with one command.
claude mcp add --transport http rytiv https://api.rytiv.com/v1/mcp --header "Authorization: Bearer <your key>"