By default every LLM call in the computer-use harness runs on Coasty's managed models. BYOK (bring your own key) flips that: opt in and the entire harness (the worker, grounding, the code agent, and compaction; every LLM call) runs on your own Anthropic or OpenAI account instead. Opt-in is always explicit, per request or per stored key. provider: "managed" (or omitting llm entirely) keeps the platform default, unchanged.
There are two ways to hand over a key. Store it once with PUT /v1/llm/keys/{provider} (encrypted with AES-256-GCM at rest; only a sha256-prefix fingerprint is ever echoed back), or send it per request in headers. A header key takes precedence over the stored key. Sending a key without a provider returns 422.
X-LLM-Provider: anthropic
X-LLM-Api-Key: sk-ant-your_key_here
X-LLM-Model: claude-sonnet-4-6The headers work on POST /v1/predict, POST /v1/runs, POST /v1/workflows/{id}/runs, POST /v1/sessions, and POST /v1/schedules. Stored keys are managed through three endpoints, gated by the llm_keys scope (granted to new live keys by default). These endpoints require a live key; sandbox keys cannot read, overwrite, or delete the production credential store:
import os, requests
BASE = "https://coasty.ai/v1"
HEADERS = {"X-API-Key": os.environ["COASTY_API_KEY"]}
# Store (upsert) your own Anthropic key. Encrypted at rest; never echoed back.
stored = requests.put(
f"{BASE}/llm/keys/anthropic",
headers=HEADERS,
json={"api_key": os.environ["ANTHROPIC_API_KEY"]},
timeout=30,
).json()
print(stored) # {"provider": "anthropic", "key_fingerprint": "a1b2c3d4", "stored": true}
# List stored keys: provider, fingerprint, timestamps. Never the key itself.
keys = requests.get(f"{BASE}/llm/keys", headers=HEADERS, timeout=30).json()
for k in keys["keys"]:
print(k["provider"], k["key_fingerprint"])
# Delete when you rotate away (404 LLM_KEY_NOT_FOUND when none is stored)
requests.delete(f"{BASE}/llm/keys/anthropic", headers=HEADERS, timeout=30)On the request body, the same endpoints accept an llm object that selects the provider and, optionally, a model per harness role. It deliberately has no api_key field (a 422 if you attempt one): keys ride headers or the encrypted store only, so they can never be echoed in run objects, webhooks, or idempotency replays.
import os, requests
BASE = "https://coasty.ai/v1"
HEADERS = {"X-API-Key": os.environ["COASTY_API_KEY"]}
# Start a run on YOUR Anthropic key (stored earlier via PUT /llm/keys/anthropic).
# The llm block deliberately has NO api_key field (422 if you try): keys ride
# headers or the encrypted store only, never request bodies.
run = requests.post(
f"{BASE}/runs",
headers=HEADERS,
json={
"machine_id": "mch_test_0123456789abcdef",
"task": "Open the billing page and download the latest invoice as PDF",
"llm": {
"provider": "anthropic", # or "openai"; "managed" = platform default
"model": "claude-sonnet-4-6", # any model on your account (vision-capable)
"compaction_model": "claude-haiku-4-5", # optional per-role override
},
},
timeout=30,
).json()
# Per-request variant: send the key in headers instead of storing it.
# A header key takes precedence over the stored key for this request only.
byok_headers = {
**HEADERS,
"X-LLM-Provider": "anthropic",
"X-LLM-Api-Key": os.environ["ANTHROPIC_API_KEY"],
"X-LLM-Model": "claude-sonnet-4-6",
}
# The run echoes a non-secret llm block; the key itself is never returned.
run = requests.get(f"{BASE}/runs/{run['id']}", headers=HEADERS, timeout=30).json()
print(run["llm"]) # {"provider": ..., "model": ..., "key_fingerprint": ..., "key_source": ..., "key_scrubbed": ...}Per-role overrides exist for tuning cost against quality: run compaction on a cheaper model while the worker stays on the default, for example. For runs, workflows, and schedules the key is snapshotted encrypted into the run, so crash-recovery on another replica keeps using your key; it is scrubbed the moment the run reaches a terminal state. GET /v1/runs/{id} echoes a non-secret llm block: {provider, model, key_fingerprint, key_source, key_scrubbed}. Schedules store only the non-secret preference; at fire time the current stored key is used. Delete the stored key and future firings fail loudly with LLM_KEY_NOT_CONFIGURED; they never silently run on platform keys.
Grounding (pixel-coordinate resolution) quality is tuned on the platform model. When running grounding on your own model, expect the best results with the defaults, and use grounding_model to experiment before committing a cheaper or different model to that role.
No silent fallback, ever: once you ask for BYOK, no code path can use Coasty's platform LLM keys. Errors from your provider surface as your key's issue with stable codes: LLM_KEY_NOT_CONFIGURED, LLM_KEY_INVALID, LLM_PROVIDER_AUTH_FAILED, LLM_PROVIDER_RATE_LIMITED, LLM_PROVIDER_QUOTA_EXCEEDED, and LLM_PROVIDER_ERROR.
Billing: Coasty's per-call and per-step platform charges are unchanged with BYOK. The model tokens are billed by your provider account directly.