Send a screenshot. Get structured mouse and keyboard actions back. One REST endpoint — for automation, browser testing, and AI agents that interact with any GUI.
{ "action_type": "click", "params": { "x": 98, "y": 136, } }
Pure REST. No SDK lock-in, no extra servers, no browser drivers.
1import requests, base6423img = base64.b64encode(open("screen.png", "rb").read()).decode()45r = requests.post(6 "https://coasty.ai/v1/predict",7 headers={"X-API-Key": "sk-coasty-live-..."},8 json={9 "screenshot": img,10 "instruction": "Click the search bar and type 'hello'",11 },12)1314for a in r.json()["actions"]:15 print(a["action_type"], a["params"])
Provision a sandbox or production VM, then drive it with actions, terminal commands, browser automation, or file ops. One auth header, fifteen endpoints.
1import requests23# Provision a sandbox VM (sk-coasty-test-* — instant, no AWS, no billing).4r = requests.post(5 "https://coasty.ai/v1/machines",6 headers={7 "X-API-Key": "sk-coasty-test-...",8 "Idempotency-Key": "demo-001",9 },10 json={11 "display_name": "automation-bot",12 "os_type": "linux",13 "desktop_enabled": True,14 },15)16machine = r.json()["machine"]1718# Drive it: click at (512, 340)19requests.post(20 f"https://coasty.ai/v1/machines/{machine['id']}/actions",21 headers={"X-API-Key": "sk-coasty-test-..."},22 json={"command": "click", "parameters": {"x": 512, "y": 340}},23)
POST /v1/machinesGET /v1/machinesDELETE /v1/machines/{id}POST /v1/machines/{id}/startPOST /v1/machines/{id}/stopPOST /v1/machines/{id}/snapshotGET /v1/machines/{id}/screenshotGET /v1/machines/{id}/connectionPOST /v1/machines/{id}/actionsPOST /v1/machines/{id}/actions/batchPOST /v1/machines/{id}/browser/{op}POST /v1/machines/{id}/terminalPOST /v1/machines/{id}/files/{op}Run an agent on a cron, fire it from any webhook with HMAC, or chain schedules together. Per-fire 10 cr/min · webhook routing 1 cr / 200 fires · sandbox is free.
1import requests, hmac, hashlib, time23# 1. Create a daily 9 AM ET schedule4sched = requests.post(5 "https://coasty.ai/v1/schedules",6 headers={"X-API-Key": "sk-coasty-test-..."},7 json={8 "name": "morning briefing",9 "machine_id": "550e8400-e29b-41d4-a716-446655440000",10 "task_prompt": "Summarize unread Gmail and post to Slack.",11 "frequency": "daily",12 "time": "09:00",13 "timezone": "America/New_York",14 },15).json()1617# 2. Add a webhook trigger — store the secret immediately18trigger = requests.post(19 f"https://coasty.ai/v1/schedules/{sched['id']}/triggers",20 headers={"X-API-Key": "sk-coasty-test-..."},21 json={"kind": "webhook"},22).json()23secret = trigger["webhook_secret"] # whsec_<64 hex> — store this2425# 3. Sign + fire the webhook from any external system26ts = int(time.time())27body = b'{"event":"order.placed"}'28sig = hmac.new(secret.encode(), f"{ts}.".encode() + body,29 hashlib.sha256).hexdigest()30requests.post(31 trigger["webhook_url"],32 headers={"Coasty-Signature": f"t={ts},v1={sig}"},33 data=body,34)
POST /v1/schedulesGET /v1/schedulesPATCH /v1/schedules/{id}DELETE /v1/schedules/{id}POST /v1/schedules/{id}/runPOST /v1/schedules/{id}/pausePOST /v1/schedules/{id}/resumeGET /v1/schedules/{id}/runsGET /v1/schedules/{id}/runs/{run_id}POST /v1/schedules/{id}/triggersDELETE /v1/schedules/{id}/triggers/{tid}POST /v1/triggers/webhook/{wh} ← unauth · HMACPOST /v1/triggers/email-mailboxNo selectors. No DOM parsing. No brittle XPath. Just vision.
Send screenshot
Base64 PNG/JPEG + plain-language intent
AI reasons visually
Vision model identifies the target UI element
Execute actions
Typed primitives: click, type, scroll, press…
Works on any UI — web, desktop, mobile, VNC. No DOM access, no selectors, no agents.
Multi-step trajectories. The model remembers what it tried, what worked, and what's next.
V3 for speed (3.5s/step, multi-action). V1 for precision (reflection, single-action).
Browser tabs, desktop apps, mobile emulators, VNC feeds — anything you can capture visually.
click, double_click, type, scroll, drag, key_press, key_combo, wait, done, fail.
Plain REST + JSON. Python, Node, Go, Ruby, PHP, Java, C#, or cURL from your terminal.
Deducted from your shared credit balance. Management endpoints always free.
POST /predict5 crPOST /sessions10 crPOST /sessions/{id}/predict4 crPOST /ground3 crPOST /ocr3 crPOST /parseFreeGET /models, /usage, /sessionsFreeSurcharges
The CUA API gives your code the ability to see and interact with any screen. Send a screenshot and a natural language instruction — receive structured mouse clicks, keyboard inputs, and scroll commands with exact coordinates.
Every request needs an X-API-Key header. Sign up to create API keys. Credits are deducted per request from your shared balance.
X-API-Key: sk-coasty-live-your_key_hereChoose your language. The predict endpoint is the core of the API — everything else builds on it.
pip install requestsimport requests, base64
API_KEY = "sk-coasty-live-..."
img = base64.b64encode(open("screen.png", "rb").read()).decode()
r = requests.post(
"https://coasty.ai/v1/predict",
headers={"X-API-Key": API_KEY},
json={
"screenshot": img,
"instruction": "Click the search bar and type 'hello'",
},
)
for action in r.json()["actions"]:
print(action["action_type"], action["params"])# Create a session for multi-step tasks
s = requests.post(
"https://coasty.ai/v1/sessions",
headers={"X-API-Key": API_KEY},
json={"cua_version": "v3", "screen_width": 1920, "screen_height": 1080},
).json()
session_id = s["session_id"]
# Send screenshots in a loop
while True:
screenshot = capture_screenshot() # your screenshot function
r = requests.post(
f"https://coasty.ai/v1/sessions/{session_id}/predict",
headers={"X-API-Key": API_KEY},
json={"screenshot": screenshot, "instruction": "Complete the form"},
).json()
for action in r["actions"]:
execute_action(action) # your action executor
if r["status"] in ("done", "fail"):
breakEvery prediction returns structured actions with exact coordinates, a status signal, and token usage.
{
"request_id": "req_abc123",
"actions": [
{
"action_type": "click",
"params": { "x": 512, "y": 340, "button": "left", "clicks": 1 }
},
{
"action_type": "type_text",
"params": { "text": "hello world" }
}
],
"reasoning": "I see a search bar at (512, 340)...",
"status": "continue",
"usage": {
"input_tokens": 1523,
"output_tokens": 245,
"credits_charged": 5
}
}clickMouse click at (x, y)type_textType a stringkey_pressPress a key (enter, tab...)key_comboCombo (ctrl+c, cmd+v...)scrollScroll at a positiondragDrag between two pointsmoveMove cursorwaitPause executiondoneTask completedfailTask impossibleOnly screenshot and instruction are required.
screenshotstringrequiredinstructionstringrequiredcua_version"v3" | "v1"screen_widthintscreen_heightintmax_actionsint (1-10)trajectoryarraysystem_promptstringtoolsstring[]Stateless prediction, sessions, and grounding utilities. All require the X-API-Key header.
/v1/predict5 cr/v1/sessions10 cr/v1/sessions/{id}/predict4 cr/v1/sessions/{id}/resetFree/v1/sessions/{id}Free/v1/ground3 cr/v1/ocr3 cr/v1/parseFree/v1/modelsFree/v1/usageFree/v1/sessionsFreeProvision a sandbox or production VM, then drive it with actions, terminal commands, browser automation, or file operations. Sandbox keys (sk-coasty-test-*) return mock VMs with no AWS billing.
machines:readlist, get, screenshotmachines:writeprovision, start, stop, terminateactions:execclick, type, scroll, browser_*terminal:execshell command executionfiles:readread, exists, listfiles:writewrite, edit, append, deletebrowser:executearbitrary JS in browsersnapshots:writecreate AMI snapshotsconnection:readfetch SSH key + VNC password20 cr min10 cr/min50 cr/hr75 cr/hr5 cr/hr1 cr1 cr / 2 GB-mo1 cr/GBFreesk-coasty-test-* key during development — you get instant mock VMs (id mch_test_…), synthetic action results, and zero billing. The wire format matches production exactly, so you can swap to a live key and ship.Create a VM, list your fleet, and control start/stop/snapshot/terminate. Sandbox keys mock everything in-memory; live keys provision real EC2 / Azure instances.
import requests
# Provision a fresh Linux desktop VM. Sandbox keys (sk-coasty-test-*)
# return a mock machine instantly with no AWS billing.
r = requests.post(
"https://coasty.ai/v1/machines",
headers={
"X-API-Key": "sk-coasty-live-...",
"Idempotency-Key": "provision-bot-001", # safe to retry
},
json={
"display_name": "automation-bot",
"os_type": "linux",
"desktop_enabled": True,
},
)
machine = r.json()["machine"]
print(machine["id"], machine["status"])/v1/machines/v1/machines/{id}/v1/machines/{id}/start/v1/machines/{id}/stop/v1/machines/{id}/snapshot/v1/machines/{id}Dispatch a single action, or chain up to 50 in one batch. Commands are validated against an explicit allowlist — typos return 422, never reach the VM.
import requests
machine_id = "..." # from provision response
r = requests.post(
f"https://coasty.ai/v1/machines/{machine_id}/actions",
headers={"X-API-Key": "sk-coasty-live-..."},
json={
"command": "click",
"parameters": {"x": 512, "y": 340},
},
)
result = r.json()
print(result["success"], result["duration_ms"], "ms")clickactions:exectypeactions:execkey_pressactions:execkey_comboactions:execscrollactions:execdragactions:execscreenshotactions:execterminal_executeterminal:execfile_readfiles:readfile_writefiles:writebrowser_navigateactions:execbrowser_clickactions:execbrowser_executebrowser:executePOST /v1/machines/{id}/actions/batch
Content-Type: application/json
X-API-Key: sk-coasty-live-...
{
"steps": [
{ "command": "browser_navigate",
"parameters": { "url": "https://example.com/login" } },
{ "command": "browser_type",
"parameters": { "selector": "#email", "text": "[email protected]" } },
{ "command": "browser_type",
"parameters": { "selector": "#password", "text": "***" } },
{ "command": "browser_click",
"parameters": { "selector": "button[type=submit]" } }
],
"stop_on_error": true
}
Returns:
{
"results": [...], // one per step
"completed_count": 4,
"failed_count": 0,
"aborted": false,
"request_id": "req_..."
}Typed convenience endpoints over /actions. Same dispatch path, ergonomic URL shapes, identical scope rules.
import requests
# Run a shell command (PowerShell on Windows, bash on Linux).
# Output is truncated VM-side to 5000 chars.
r = requests.post(
f"https://coasty.ai/v1/machines/{machine_id}/terminal",
headers={"X-API-Key": "sk-coasty-live-..."},
json={
"command": "uname -a && uptime",
"timeout_ms": 10_000,
},
)
print(r.json()["result"]["output"])/browser/{op}opennavigateclicktypedomclickablesstateinfoscrollclosescreenshotwaitlist-tabsopen-tabclose-tabswitch-tabBody: { parameters: {…}, timeout_ms? }. browser_execute NOT here — use /actions with browser:execute.
/files/{op}readexistslistlist-directorydownloadlist-downloadswriteeditappenddeletedelete-directory/terminal{ command, timeout_ms?, session_id?, cwd? }PowerShell on Windows, bash on Unix. Output capped at 5000 chars VM-side. Pass session_id to reuse a persistent shell across calls.Requires terminal:exec scope.Full reference. All require X-API-Key (or Authorization: Bearer) except /health.
/v1/machines20 cr min/v1/machinesFree/v1/machines/{id}Free/v1/machines/{id}Free/v1/machines/{id}/startFree/v1/machines/{id}/stopFree/v1/machines/{id}/snapshot1 cr/v1/machines/{id}/actionsFree/v1/machines/{id}/actions/batchFree/v1/machines/{id}/browser/{op}Free/v1/machines/{id}/terminalFree/v1/machines/{id}/files/{op}Free/v1/machines/{id}/screenshotFree/v1/machines/{id}/connectionFree/v1/machines/healthFreeCron-fired agent runs, one-shot run_at jobs, plus three trigger kinds (webhook, email, chain). Schedules created via API show up in your /schedules dashboard automatically.
schedules:readlist, get, runs, triggersschedules:writecreate, update, delete, pause, run-nowtriggers:writeadd/remove webhook, email, chain triggers20 cr min10 cr/min1 cr / 2001 cr / 10FreeFreeFreeCreate a cron or one-shot schedule. Pause, resume, run-now, list runs, soft-delete. Idempotency-Key supported on POST.
import requests
# Daily 9:00 AM ET email summary, fired by the Coasty scheduler.
# Per-fire cost: 10 credits/min while the agent runs.
r = requests.post(
"https://coasty.ai/v1/schedules",
headers={
"X-API-Key": "sk-coasty-live-...",
"Idempotency-Key": "morning-briefing-001",
},
json={
"name": "morning briefing",
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"task_prompt": "Summarize unread Gmail and post the top 5 to Slack.",
"frequency": "daily",
"time": "09:00",
"timezone": "America/New_York",
},
)
schedule = r.json()
print(schedule["id"], schedule["next_run_at"])every_15_minutes*/15 * * * *every_30_minutes*/30 * * * *hourly0 * * * *every_6_hours0 */6 * * *every_12_hours0 */12 * * *daily0 9 * * * (override with `time`)weekly0 9 * * 1 (override `time`, `day_of_week`)monthly0 9 1 * * (override `time`, `day_of_month`)customsupply your own `cron` fieldPOST /v1/schedules
Content-Type: application/json
X-API-Key: sk-coasty-live-...
{
"name": "launch announcement",
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"task_prompt": "Post the launch tweet from the draft.",
"run_at": "2099-01-01T17:00:00Z"
}
# Notes:
# * `run_at` and `frequency` are mutually exclusive.
# * Must be in the future (within last 60s tolerated).
# * After firing once, the schedule auto-pauses with paused_reason='one_shot_complete'.max_consecutive_failures (default 5) failed runs. Resume via POST /v1/schedules/{id}/resume. Insufficient credits at fire-time auto-pauses with reason insufficient_credits.Three trigger kinds. Webhook secrets are returned ONCE — store them on creation.
# Add a webhook trigger — returns the signing secret ONCE.
r = requests.post(
f"https://coasty.ai/v1/schedules/{schedule_id}/triggers",
headers={"X-API-Key": "sk-coasty-live-..."},
json={"kind": "webhook", "rate_limit_per_minute": 60},
)
trigger = r.json()
webhook_url = trigger["webhook_url"] # https://coasty.ai/v1/triggers/webhook/whk_...
webhook_secret = trigger["webhook_secret"] # whsec_<64 hex> — STORE THIS
# Save webhook_secret in your secrets manager. We hash + persist it; we
# cannot show it again. Lose it = generate a new trigger.{ kind: "webhook" }webhook_url + webhook_secret (whsec_64hex). Sign every fire with HMAC-SHA256(secret, "{ts}.body") and send Coasty-Signature: t={ts},v1={sig}. Replay window 5 min. Idempotent on identical (id, body) within 60 s.{ kind: "email" }<label>.<rand>@agents.coasty.ai address. Inbound emails fire the schedule. email_label must match ^[a-z0-9][a-z0-9._-]{0,32}[a-z0-9]$.{ kind: "chain" }source_schedule_id completes. Events: on_complete · on_failure · on_any.Max chain depth: 5./v1/schedules/{id}/triggers/v1/schedules/{id}/triggers/v1/schedules/{id}/triggers/{trigger_id}/v1/triggers/email-mailboxPOST /v1/triggers/webhook/{webhook_id} — UNAUTHENTICATED but HMAC-verified. Hit by Stripe, Linear, n8n, anything that can sign a request.
# Customer-side webhook signing — produces a Coasty-Signature header
# the public /v1/triggers/webhook/{id} endpoint accepts.
import hmac, hashlib, time
def sign_coasty_webhook(secret: str, body: bytes) -> dict:
ts = int(time.time())
signed_payload = f"{ts}.".encode("utf-8") + body
sig = hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
return {"Coasty-Signature": f"t={ts},v1={sig}"}
# Fire the webhook from your own app:
import requests
body = b'{"event":"order.placed","order_id":"123"}'
headers = {**sign_coasty_webhook(webhook_secret, body), "Content-Type": "application/json"}
requests.post(webhook_url, data=body, headers=headers)Coasty-Signature: t=<unix_ts>,v1=<hmac_sha256_hex>
# t = current unix timestamp (seconds)
# v1 = lowercase hex HMAC-SHA256(webhook_secret, "<t>.<body>")
# (period as separator; raw body bytes; no newline)
# replay = signatures > 5 min stale are rejected
# dedup = identical (webhook_id, body) within 60 s returns deduplicated=true
# body cap = 1 MB (413 if exceeded)HTTP/1.1 200 OK
Content-Type: application/json
X-Coasty-Request-Id: req_...
X-Coasty-Webhook-Deduplicated: false
{
"received": true,
"schedule_id": "550e8400-...",
"run_id": "550e8400-...",
"deduplicated": false,
"message": "Schedule fire dispatched.",
"request_id": "req_..."
}webhook_secret like a password — it grants the ability to fire your schedule. Coasty stores it server-side and uses it to verify every inbound signature. If leaked: delete the trigger and re-create to rotate.Full reference. Public webhook fire endpoint is the only one without auth (it uses HMAC-signed Coasty-Signature instead).
/v1/schedules20 cr min/v1/schedulesFree/v1/schedules/{id}Free/v1/schedules/{id}Free/v1/schedules/{id}Free/v1/schedules/{id}/pauseFree/v1/schedules/{id}/resumeFree/v1/schedules/{id}/run10 cr/min/v1/schedules/{id}/runsFree/v1/schedules/{id}/runs/{run_id}Free/v1/schedules/{id}/triggersFree/v1/schedules/{id}/triggersFree/v1/schedules/{id}/triggers/{trigger_id}Free/v1/triggers/email-mailboxFree/v1/triggers/webhook/{webhook_id}1 cr / 200Drive Coasty from any MCP-capable client — Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot. One install, every Coasty tool available.
MCP (Model Context Protocol) is the open standard, designed by Anthropic and adopted across the agent ecosystem, that lets LLM hosts plug into external tools and data. Coasty's MCP server is a thin wrapper over the /v1 API — same scopes, same rate limits, same billing. It runs locally via npx; your API key never touches a Coasty MCP relay.
npm i -g @coasty/mcpOr just point your MCP host at npx -y @coasty/mcp — zero install needed. Set COASTY_API_KEY in the host config and you're running. Sandbox keys (sk-coasty-test-*) work for free.
Pick your client. Configs are checked into the @coasty/mcp test suite — copying any of these blocks verbatim produces a working install.
// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"coasty": {
"command": "npx",
"args": ["-y", "@coasty/mcp"],
"env": { "COASTY_API_KEY": "sk-coasty-test-..." }
}
}
}
// Restart Claude Desktop. Coasty tools appear under the 🛠 icon.claude mcp add coasty \
--env COASTY_API_KEY=sk-coasty-test-... \
-- npx -y @coasty/mcp
# Verify
claude mcp list
# coasty ✓ connected (24 tools, 2 prompts){
"mcpServers": {
"coasty": {
"command": "npx",
"args": ["-y", "@coasty/mcp"],
"env": { "COASTY_API_KEY": "sk-coasty-test-..." }
}
}
}
// Cursor → Settings → MCP shows a green dot when reachable.{
"mcpServers": {
"coasty": {
"command": "npx",
"args": ["-y", "@coasty/mcp"],
"env": { "COASTY_API_KEY": "sk-coasty-test-..." }
}
}
}// VS Code uses "servers" (NOT "mcpServers"). Tools only appear in
// Agent mode — type # in the chat to autocomplete tool names.
{
"servers": {
"coasty": {
"command": "npx",
"args": ["-y", "@coasty/mcp"],
"env": { "COASTY_API_KEY": "sk-coasty-test-..." }
}
}
}sk-coasty-test-* key while you're iterating — everything works (provision, schedule, run) but no real EC2 / Azure / credit billing. Swap in sk-coasty-live-* when you're ready to ship.24 tools across Predict, Machines, Schedules, and Account. All carry MCP annotations (readOnly / destructive / idempotent) so well-behaved hosts confirm before destructive operations.
coasty_predictScreenshot + goal → list of actionscoasty_groundElement description → (x, y) coordscoasty_ocrScreenshot → text + bounding boxescoasty_parsepyautogui code → structured actions (free)coasty_list_machinesRead-only — your VMscoasty_get_machineRead-only — one VMcoasty_take_machine_screenshotRead-only — current desktop imagecoasty_provision_machineCreate new VM (idempotent w/ key)coasty_terminate_machineDestructive — irreversiblecoasty_start_machineResume a stopped VMcoasty_stop_machinePause running VM (preserves state)coasty_execute_machine_actionDispatch click / type / scroll / browser_* / file_* / etc.coasty_run_terminal_commandShell exec on VM (terminal:exec scope)coasty_list_schedulesRead-onlycoasty_get_scheduleRead-onlycoasty_list_schedule_runsCursor-paginated historycoasty_create_scheduleCron, run-once, or custom — appears in dashboardcoasty_update_schedulePATCH (any field)coasty_delete_scheduleDestructive — soft-deletecoasty_run_schedule_nowManual fire (idempotent w/ key)coasty_pause_scheduleDisable future firescoasty_resume_scheduleRe-enablecoasty_add_triggerWebhook / email / chain (HMAC secret returned ONCE)coasty_remove_triggerDestructivecoasty_get_creditsRead-only — balance + tier + period usagestart_automation_session — pre-fill a chat that picks a VM, screenshots, predicts, and executes toward a goal.debug_failed_run — investigate why a schedule has been failing; proposes concrete fixes.readOnlyHint, destructiveHint, idempotentHint, and openWorldHint so a well-configured host can auto-approve safe reads and require explicit consent for destructive ops.All errors return a JSON body with error.code, error.message, error.type, and error.request_id fields.
INVALID_MACHINE_IDPath id is not a UUID or mch_test_<hex>INVALID_IDEMPOTENCY_KEYIdempotency-Key has bad chars or > 128 charsUNKNOWN_BROWSER_OPUnknown {op} in /browser/{op}UNKNOWN_FILE_OPUnknown {op} in /files/{op}INVALID_API_KEYMissing or invalid X-API-Key / Bearer tokenINSUFFICIENT_CREDITSBalance below required amount (provision needs ≥ 20 cr)INSUFFICIENT_SCOPEAPI key lacks the required scope for this opNOT_FOUNDMachine/session not found OR not owned by your keyINVALID_STATEAction requires status='running'; lifecycle has illegal transitionIDEMPOTENCY_KEY_REUSEDSame Idempotency-Key sent with a different request bodyVALIDATION_ERRORBody fails Pydantic — unknown field, wrong type, oversize, bad commandRATE_LIMIT_EXCEEDEDToo many requests — see Retry-After headerTEST_MACHINE_LIMITSandbox keys are capped at 5 mock VMsSCREENSHOT_FAILEDScreenshot dispatch reached the VM but capture erroredDB_UNAVAILABLEBackend cannot reach SupabaseUPSTREAM_TIMEOUTProvision proxy timed out (try Idempotency-Key + retry)INVALID_SCHEDULE_IDschedule_id is not a UUID or sch_test_<hex>INVALID_TRIGGER_IDtrigger_id is not 'trg_<hex>'INVALID_RUN_IDrun_id contains non-allowlisted charsINVALID_LIMITlimit must be 1..200INVALID_STATUS_FILTERUnknown ?status= value on /runsEMPTY_UPDATEPATCH body has no fieldsINVALID_SIGNATUREMissing/malformed/stale/tampered Coasty-SignatureSOURCE_SCHEDULE_NOT_FOUNDChain trigger source not owned by your keyRUN_NOT_FOUNDRun id not in this schedule's historyWEBHOOK_DISABLEDWebhook trigger has been disabled by the ownerTEST_SCHEDULE_LIMITSandbox keys are capped at 10 mock schedulesSCHEDULE_LIMIT_REACHEDPer-tier slot limit (free:3 / pro:10 / enterprise:50)Free account, free keys, free credits to start. No card required.